package com.skillworld.webapp.model.bag;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.OneToOne;
import javax.persistence.OrderBy;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import javax.persistence.Transient;
import javax.persistence.Version;

import org.hibernate.annotations.Cascade;
import org.hibernate.annotations.CascadeType;

import com.skillworld.webapp.model.item.Item;
import com.skillworld.webapp.model.user.User;

import es.udc.pojo.modelutil.exceptions.InstanceNotFoundException;


@Entity
@Table(name="Inventory")
public class Bag 
{

    private Long inventoryId;
    private int capacity;
    /*
     * Invariant: forall entry, entry.getUsesLeft() > 0 
     */
    private List<ItemRecord> entries = new ArrayList<ItemRecord>();
    private User user;
    
    
    public Bag()
    {

    }

    public Bag(int capacity) 
    {
        this.capacity = capacity;
    }
    

    @SequenceGenerator(name="InventoryIdGenerator",sequenceName="InventorySeq")
    @Id
    @GeneratedValue(strategy=GenerationType.AUTO,generator="InventoryIdGenerator")
    @Column(name="idInventory")   
    public Long getInventoryId() {
        return inventoryId;
    }

    public void setInventoryId(Long inventoryId) {
        this.inventoryId = inventoryId;
    }

    @Column(name="maxSize")
    public int getCapacity() {
        return capacity;
    }

    protected void setCapacity(int capacity) {
        this.capacity = capacity;
    }
    
    @Transient
    public boolean isFull()
    {
        return capacity <= getEntries().size();
    }

    /*
     * Some entries may be dirty (expired)
     */
    @OneToMany(mappedBy="bag",orphanRemoval=true)
    @Cascade(CascadeType.SAVE_UPDATE)
    @OrderBy
    public List<ItemRecord> getEntries() {
        return entries;
    }

    protected void setEntries(List<ItemRecord> items) {
        this.entries = items;
    }

    @Transient
    public int getSize()
    {
        return getEntries().size();
    }
    
    /*
     * Returns only valid entries
     */
    @Transient
    public Set<ItemRecord> getEntries(Item item)
    {
        Set<ItemRecord> itemEntries = new HashSet<ItemRecord>();
        for (ItemRecord entry : getEntries())
        {
            if (entry.getItem().equals(item) && !entry.isExpired())
            { itemEntries.add(entry); }
        }

        return itemEntries;
    }
 
    /*
     * Considers valid entries only
     */
    public ItemRecord findItem(Item item)
    {
        Iterator<ItemRecord> iterator = getEntries(item).iterator();
        if (iterator.hasNext())
        { return iterator.next(); }
        else
        { return null; }
    }
    
    /*
     * Considers valid entries only
     */
    public boolean hasItem(Item item)
    {
        return !getEntries(item).isEmpty();
    }
    
    protected void addEntry(ItemRecord entry)
    {
        this.getEntries().add(entry);
        entry.setBag(this);
    }
    
    public void copyEntry(ItemRecord entry)
    {
        addEntry(entry.clone());
    }
    
    public void removeEntry(ItemRecord entry)
    {
        this.getEntries().remove(entry);
        entry.setBag(null);
    }
    
    public ItemRecord addItem(Item item)
    {
        ItemRecord record = new ItemRecord(item);
        this.addEntry(record);
        
        return record;
    }
 
    public void addItem(Item item, Calendar expirationDate)
    {
        ItemRecord record = new ItemRecord(item,expirationDate);
        this.addEntry(record);
    }
    
    public void cleanup()
    {
        Set<ItemRecord> dirty = new HashSet<ItemRecord>();
        for (ItemRecord entry : getEntries())
        {
            if (entry.isExpired())
            { dirty.add(entry); }
        }

        getEntries().removeAll(dirty);
    }
    
    public void consumeItem(Item item)
        throws InstanceNotFoundException
    {
        ItemRecord entry = findItem(item);
        
        if (entry == null)
        { throw new InstanceNotFoundException(item.getItemId(), Item.class.getName()); }
        
        entry.consume();
        
        if (entry.getUsesLeft() == 0)
        { removeEntry(entry); }
    }

    @OneToOne(optional=false,mappedBy="bag",fetch=FetchType.LAZY)
    public User getUser() {
        return user;
    }

    public void setUser(User user) {
        this.user = user;
    }
}
